/*
 * eID Test PKI Project.
 * Copyright (C) 2012 FedICT.
 * Copyright (C) 2012 Frank Cornelis.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.eid.pki.model;

import java.io.InputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.CRLException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;

import be.fedict.eid.pki.entity.CertificateAuthorityEntity;
import be.fedict.eid.pki.exception.ExistingCertificateAuthorityException;
import be.fedict.eid.pki.exception.InvalidPolicyException;
import be.fedict.eid.pki.exception.InvalidX500NameException;

@Stateless
public class CertificateAuthorityManagerBean {

	private static final Log LOG = LogFactory
			.getLog(CertificateAuthorityManagerBean.class);

	@PersistenceContext
	private EntityManager entityManager;

	@EJB
	private PKIGeneratorBean pkiGeneratorBean;

	@EJB
	private StorageBean storageBean;

	@Resource
	private TimerService timerService;

	@EJB
	private IntermediateCAManagerBean intermediateCAManagerBean;

	public List<CertificateAuthorityEntity> getCertificateAuthorities() {
		return CertificateAuthorityEntity
				.getCertificateAuthorities(this.entityManager);
	}

	public void addCertificateAuthority(String name, String subject,
			int keySize, int validity, String policyOid, String cps,
			int crlValidity, int crlOverlap, String interSubject,
			String interPolicyOid, int interValidity, int interSpawnInterval,
			int interSpawnCount) throws ExistingCertificateAuthorityException,
			InvalidX500NameException, InvalidPolicyException {
		CertificateAuthorityEntity certificateAuthorityEntity = this.entityManager
				.find(CertificateAuthorityEntity.class, name);
		if (null != certificateAuthorityEntity) {
			throw new ExistingCertificateAuthorityException();
		}
		KeyPair keyPair = this.pkiGeneratorBean.generateKeyPair(keySize);
		DateTime notBefore = new DateTime();
		DateTime notAfter = notBefore.plusMinutes(validity);
		X509Certificate certificate = this.pkiGeneratorBean
				.generateCACertificate(keyPair, subject, notBefore, notAfter,
						policyOid, cps);
		DateTime thisUpdate = notBefore;
		DateTime nextUpdate = notBefore.plusMinutes(crlValidity + crlOverlap);
		LOG.debug("CRL thisUpdate: " + thisUpdate);
		LOG.debug("CRL nextUpdate: " + nextUpdate);
		DateTime nextCrlGeneration = notBefore.plusMinutes(crlValidity);
		TimerConfig timerConfig = new TimerConfig(name, false);
		this.timerService.createSingleActionTimer(nextCrlGeneration.toDate(),
				timerConfig);
		X509CRL crl = this.pkiGeneratorBean.generateCrl(certificate,
				keyPair.getPrivate(), thisUpdate, nextUpdate);
		String crlResourceName = getCrlResourceName(name);
		try {
			this.storageBean.store(crlResourceName, crl.getEncoded());
		} catch (CRLException e) {
			throw new RuntimeException(e);
		}
		certificateAuthorityEntity = new CertificateAuthorityEntity(name,
				subject, keySize, notBefore, notAfter, certificate,
				crlValidity, crlOverlap, keyPair.getPrivate(),
				interSubject, interPolicyOid, interValidity,
				interSpawnInterval, interSpawnCount);
		this.entityManager.persist(certificateAuthorityEntity);
		this.intermediateCAManagerBean.init(certificateAuthorityEntity);
	}

	public void removeCertificateAuthority(
			CertificateAuthorityEntity certificateAuthority) {
		String caName = certificateAuthority.getName();
		CertificateAuthorityEntity certificateAuthorityEntity = this.entityManager
				.find(CertificateAuthorityEntity.class, caName);
		for (Timer timer : this.timerService.getTimers()) {
			String name = (String) timer.getInfo();
			if (name.equals(caName)) {
				timer.cancel();
			}
		}
		this.entityManager.remove(certificateAuthorityEntity);
		String crlResourceName = getCrlResourceName(caName);
		this.storageBean.remove(crlResourceName);
		this.intermediateCAManagerBean.remove(certificateAuthorityEntity);
	}

	public CertificateAuthorityEntity getCertificateAuthority(String caName) {
		CertificateAuthorityEntity certificateAuthorityEntity = this.entityManager
				.find(CertificateAuthorityEntity.class, caName);
		return certificateAuthorityEntity;
	}

	private String getCrlResourceName(String caName) {
		String crlResourceName = caName + ".crl";
		return crlResourceName;
	}

	public InputStream getCRL(String caName) {
		String crlResourceName = getCrlResourceName(caName);
		InputStream crlInputStream = this.storageBean
				.getInputStream(crlResourceName);
		return crlInputStream;
	}

	@Timeout
	private void timeout(Timer timer) {
		LOG.debug("timeout");
		String caName = (String) timer.getInfo();
		LOG.debug("CA name: " + caName);
		CertificateAuthorityEntity certificateAuthorityEntity = this.entityManager
				.find(CertificateAuthorityEntity.class, caName);
		if (null == certificateAuthorityEntity) {
			LOG.warn("missing CA entity: " + caName);
			return;
		}
		int crlNumber = certificateAuthorityEntity.getNextCrlNumber();
		LOG.debug("CRL number: " + crlNumber);
		DateTime notBefore = new DateTime(
				certificateAuthorityEntity.getNotBefore());
		int crlValidity = certificateAuthorityEntity.getCrlValidity();
		int crlOverlap = certificateAuthorityEntity.getCrlOverlap();
		DateTime nextCrlGeneration = notBefore.plusMinutes(crlValidity
				* crlNumber);
		DateTime notAfter = new DateTime(
				certificateAuthorityEntity.getNotAfter());
		DateTime thisUpdate = notBefore.plusMinutes(crlValidity
				* (crlNumber - 1));
		DateTime nextUpdate = thisUpdate.plusMinutes(crlValidity + crlOverlap);
		LOG.debug("CRL thisUpdate: " + thisUpdate);
		LOG.debug("CRL nextUpdate: " + nextUpdate);
		if (thisUpdate.isAfter(notAfter)) {
			LOG.debug("previous CRL was the last one");
			return;
		}
		LOG.debug("next CRL generation: " + nextCrlGeneration);
		TimerConfig timerConfig = new TimerConfig(caName, false);
		this.timerService.createSingleActionTimer(nextCrlGeneration.toDate(),
				timerConfig);
		PrivateKey privateKey = certificateAuthorityEntity.getPrivateKey();
		X509Certificate issuerCertificate = certificateAuthorityEntity
				.getCertificate();
		X509CRL crl = this.pkiGeneratorBean.generateCrl(issuerCertificate,
				privateKey, thisUpdate, nextUpdate);
		String crlResourceName = getCrlResourceName(caName);
		try {
			this.storageBean.store(crlResourceName, crl.getEncoded());
		} catch (CRLException e) {
			throw new RuntimeException(e);
		}
	}
}
